﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Ndo.Maths.MOP
{
    /// <summary>
    /// Nondominated Sorting Genetic Algorithm
    /// the default CrowdEvaluator is CrowedDistance(measure by Euclidean Distance);
    /// </summary>
    /// <typeparam name="Individual"></typeparam>
    /// <typeparam name="Vector"></typeparam>
    public class NSGA2<Individual, Vector> : MOEA<Individual, Vector>
        where Individual : XY<Vector, Vector>, new()
        where Vector : class, IList<double>, ICloneable
    {
        public NSGA2()
        {
            Selection = NondominatedSortingSelection;
            PopulationChanged += PC;
        }

        private void PC(EA<Individual, Vector, Vector> ea)
        {
            IList<ICollection<Individual>> list = new List<ICollection<Individual>>(GetFronts(Population));
            Console.WriteLine("GN[{0}] ---> domination count: {1}; levels count: {2}; NondominatedSet proportion: {3}", GenerationNumber, GetDominatingCount(), list.Count, (double)list[0].Count/(double)Population.Count);
        }

        public IList<Individual> NondominatedSortingSelection(IList<Individual> inds, int limit)
        {
            List<Individual> list = new List<Individual>(limit);
            foreach (ICollection<Individual> front in GetFronts(inds))
            {
                int d = list.Count + front.Count - limit;
                if(d > 0)
                {
                    Dictionary<Individual, Vector> dict = new Dictionary<Individual, Vector>();
                    Dictionary<Vector, Individual> opdict = new Dictionary<Vector, Individual>(new RefEqualityComparer());
                    foreach (Individual individual in front)
                    {
                        dict[individual] = individual.Y;
                        opdict[individual.Y] = individual;
                    }
                    List<Vector> environment = new List<Vector>();
                    foreach (Individual ind in inds)
                    {
                        if(!dict.ContainsKey(ind))
                            environment.Add(ind.Y);
                    }
                    foreach (Vector vector in Evaluation.Prune(dict.Values, limit - list.Count, environment))
                    {
                        list.Add(opdict[vector]);
                    };
                    break;
                }
                else if(d == 0)
                {
                    list.AddRange(front);
                    break;
                }
                list.AddRange(front);
            }
            return list;
        }
        public IEnumerable<ICollection<Individual>> GetFronts(ICollection<Individual> inds)
        {
            Dictionary<Individual, object> dict = new Dictionary<Individual, object>(inds.Count);
            foreach (Individual ind in inds)
            {
                dict[ind] = null;
            }
            while(dict.Count>0)
            {
                ICollection<Individual> front = GetNondominatedSet(dict.Keys);
                yield return front;
                foreach (Individual individual in front)
                {
                    dict.Remove(individual);
                }
            }
        }
    }
}
